package com.qf.base.utils;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * 雪花算法的结构
 * 第一部分：工作号
 * 第二部分：41位的时间错
 * 第三部分：10位的机器号（一般分裂为5+5的结构，使用硬件id+实例id）
 * 第四部分：序列号12位
 * 题：分布式的唯一di生成的策略
 * redis查一回自增一次
 * 1+41+10+12=64位
 * 雪花算法得保证集群节点时间服务器配置一致才行，为什么要配置时间服务器
 * 因为所有节点时间服务器相同才可以保证雪花算法生成的id全局递增
 * 位什么要递增呢，因为树的调平 那么根据二叉树的平衡就相对代价低，
 */
@Component
public class SnowFlakeUtil {
    public SnowFlakeUtil(){
        if (workerId>maxWorkerId||workerId<0){
            throw new IllegalArgumentException("workerId 不合法");
        }
        if (datacenterId>maxDatacenterId||datacenterId<0){
            throw new IllegalArgumentException("datacenterId 不合法");
        }
    }

    //硬件id  要小于5，大于0
    @Value("${snowflake.workerId:0}")
    private long workerId;
    //定义数据实例号
    @Value("${snowflake.datacenterId:0}")
    private long datacenterId;
    //序列号
    @Value("${snowflake.sequence:0}")
    private long sequence;
    //时间戳
    //system.currentTimeMillis  返回值时long类型
    //41位不够用    1288834974657 1970-01-01 00：00：00
    //time=system.currentTimeMillis-1288834974657
    private long twepoch=1288834974657L;  //当前时间
    //控制10位的机器号，由5位的工作好和5位的数据实例号组成
    private long workerIdBits=5L;
    private long datacenterIdBits=5L;
    /**
     * 最大值的一般写法
     * n位二进制数最大值就是  -1^(-1<<n)
     * 以字节为例
     * -1原码
     * 1000 0001
     */
    private long maxWorkerId=-1L^(-1L<<workerIdBits);
    private long maxDatacenterId=-1L^(-1L<<datacenterIdBits);

    /**
     * 序列号长度
     */
    private long sequenceBits=12L;
    /**
     * 序列号的最大值
     *
     */
    private long maxSequence=-1L^(-1L<<sequenceBits);

    /**
     * 拼装上述4个部分，分别每个部分需要移位
     * 就像Color表达颜色把rgb移位一样
     * 定义移位的多少
     */
    private long workerIdShift=sequenceBits;
    private long datacenterIdShift=sequenceBits+workerIdShift;
    private long timestampShift=sequenceBits+workerIdShift+datacenterIdBits;

    /**
     * 总体的时间戳
     */
    private long lastTimestamp=-1L;

    /**
     * 封装系统获取时间的方法
     * @return返回当前时间
     */
    private long time(){
        return System.currentTimeMillis();
    }

    /**
     * 为了保证每一次获取的新的时间戳都一定时递增的，00001
     * @return 比lastTimestamp大的一个时间
     */
    private long nextTime(long lastTimestamp){
        long timestamp=time();
        while (timestamp<=lastTimestamp){
            timestamp=time();
        }
        return timestamp;
    }
    /**
     * 雪花算法的具体实现
     */
    public synchronized long nextId(){
        //获取当前时间
        long timestamp=time();
        //比对上一次的时间
        //如果小于上一次获取的时间错，说明这个服务器时钟被调整了，返回-1表示错误
        if (timestamp<lastTimestamp){
            return -1;
        }
        //作为并发算法，不可能1毫秒只产生一个id
        //如果一毫秒只产生一个id，不如直接用时间戳就好了
        if (lastTimestamp==timestamp){
            //时间戳相同，就让序号递增
            //利用&运算放置超过最大值
            //1111
            //1111
            //当sequence+1超过maxsequence就会让sequence=0
            sequence=(sequence+1)&maxSequence;
            if (sequence==0){
                //这一毫秒中，依旧结晶所能发出idl
                //这一毫秒中，sequence已经满了
                //只能等待下一毫秒再发id
                timestamp=nextTime(lastTimestamp);

            }
        }else {
            //timestamp>lastTimestamp
            //时间时新的时间，就让序号归零
            sequence=0;
        }
        //让lastTimestamp刷新
        lastTimestamp=timestamp;

        return ((timestamp-twepoch)<<timestampShift)|
                (datacenterId<<datacenterIdBits)|
                (workerId<<workerIdBits)|
                sequence;
    }



}
